﻿using System;
using System.Collections.Generic;
using System.Text;
using CoreCalc.SheetDefinedFunctions.Accellerator;

namespace CoreCalc.SheetDefinedFunctions
{
  class AccExpressionBuilder : IExpressionVisitor
  {
      static Dictionary<FullCellAddr, AccExpr> exprCache;
      static Dictionary<float, AccExpr> constCache;

      //TEST
      static int count = 0;
      // END TEST

    private FullCellAddr thisFca; // The cell containing the Expr being translated
    private AccExpr result ;        // The result of the compilation is left here
    private SdfInfo sdfInfo;


    private AccExpressionBuilder(FullCellAddr addr, SdfInfo sdfInfo)
    {
        
      this.sdfInfo = sdfInfo;
      thisFca = addr;
    }

    public static AccExpr BuildExpression(FullCellAddr addr, SdfInfo sdfInfo)
    {
        count = 0;
        exprCache = new Dictionary<FullCellAddr, AccExpr>();
        constCache = new Dictionary<float, AccExpr>();
        AccExpr top = InternalBuildExpression(addr, sdfInfo);
        Console.WriteLine("------------------------");
        Console.WriteLine("TOTAL COUNT OF NEW ACC NODES: " + count);
        Console.WriteLine("------------------------");
        return top;
    }
    private static AccExpr TryAccNode(FullCellAddr addr, AccExpr newExpr) {
        if (newExpr is AccConst)
            return TryAccNode((newExpr as AccConst).Value, newExpr);
        
        AccExpr n;

        if (!exprCache.TryGetValue(addr, out n) && !exprCache.ContainsValue(newExpr))
        {
            n = newExpr;
            exprCache.Add(addr, n);
            count++;
            try
            {
                Console.WriteLine("- :" + newExpr.GetType());
            }
            catch (NullReferenceException ex) { }
        }

        return n;
    }
    private static AccExpr TryAccNode(float val, AccExpr newNode)
    {
        AccExpr n;

        if (!constCache.TryGetValue(val, out n))
        {
            n = newNode;
            constCache.Add(val, n);
            count++;
            try
            {
                Console.WriteLine("- :" + newNode.GetType());
            }
            catch (NullReferenceException ex) { }
        }

        return n;
    }

    

    private static AccExpr InternalBuildExpression(FullCellAddr addr, SdfInfo sdfInfo)
    {
      Cell cell;
      if (!addr.TryGetCell(out cell))
        return null;
    
      else if (cell is NumberCell) //TODO: Only if the cell is inside the function sheet!!!!!
      {
          return TryAccNode(addr, new AccConst((float)((NumberCell)cell).value.value));
      }
      else if (cell is Formula)
      {
          // Translate the expr relative to its containing cell at addr
          AccExpressionBuilder cgBuilder = new AccExpressionBuilder(addr, sdfInfo);
          Expr expr = ((Formula)cell).Expr;
          expr.VisitorCall(cgBuilder);
          return TryAccNode(addr, cgBuilder.result);
      }
      else
          throw new ImpossibleException("AcceleratorBuildExpression: " + cell);
    }

    public void CallVisitor(CellArea cellArea)
    {
        result = new AccCellArea(cellArea.ArrayView(this.thisFca));
    }

    public void CallVisitor(CellRef cellRef)
    {
        FullCellAddr addr = cellRef.GetAbsoluteAddr(this.thisFca);

        var u = Array.FindIndex<FullCellAddr>(sdfInfo.inputCells, (Predicate<FullCellAddr>)delegate(FullCellAddr x) { return x.Equals(addr); });

        if (u != -1)
        { // The Cell is an inputcell
            result = TryAccNode(addr, new AccInput(u));
        }
        else
        {
            result = TryAccNode(addr, InternalBuildExpression(addr, sdfInfo));
        }
    }

    private void HandleIfStatement(FunCall funCall)
    {
        if (funCall.es.Length != 3)
        {
            throw new Exception("AND, OR and similar are not handled in IF statements");
        }

       
        
        AccExpr accTrueLeaf;
        AccExpr accFalseLeaf;

        AccExpr accLeft;
        AccExpr accRight;

        FunCall condidtion = (FunCall)funCall.es[0];

        Expr leftside =  condidtion.es[0];
        Expr rightside = condidtion.es[1];

        string op = condidtion.function.name;
        
        Expr trueLeaf = funCall.es[1];
        Expr falseLeaf = funCall.es[2];

        trueLeaf.VisitorCall(this);
        accTrueLeaf = result;

        falseLeaf.VisitorCall(this);
        accFalseLeaf = result;
        
        leftside.VisitorCall(this);
        accLeft = result;

        rightside.VisitorCall(this);
        accRight = result;
        AccCond.Type opT;
        if (op == "=")
        {
            opT = AccCond.Type.EQ;
        }
        else if (op == ">")
        {
            opT = AccCond.Type.GT;
        }
        else if (op == ">=")
        {
            opT = AccCond.Type.GE;
        }
        else if (op == "<")
        {   
            opT = AccCond.Type.LT;
        }
        else if (op == "<=")
        {
            opT = AccCond.Type.LE;
        }
        else if (op == "<>")
        {
            opT = AccCond.Type.NQ;
        }
        else
            throw new Exception("Unknown operator in IF");

        

        result = new AccCond(opT,accLeft,accRight, accTrueLeaf, accFalseLeaf);
    }

    public void CallVisitor(FunCall funCall)
    {
        if (funCall.function.name == "IF")
        {
            HandleIfStatement(funCall);
            return;
        }

      AccExpr[] expressions = new AccExpr[funCall.es.Length];
      for (int i=0; i<funCall.es.Length; i++)
      {
        funCall.es[i].VisitorCall(this);
        expressions[i] = result;
        
      }

      if (funCall.function.name == "+")
      {
          var node = new AccBinaryOp(AccBinaryOp.Type.Add);
          node.Child1 = expressions[0];
          node.Child2 = expressions[1];

          result = node ;
      }
      else if (funCall.function.name == "-")
      {
          var node = new AccBinaryOp(AccBinaryOp.Type.Sub);

          node.Child1 = expressions[0];
          node.Child2 = expressions[1];

          result = node;
      }
      else if (funCall.function.name == "*")
      {
          var node = new AccBinaryOp(AccBinaryOp.Type.Mul);
          node.Child1 = expressions[0];
          node.Child2 = expressions[1];

          result = node;
      }
      else if (funCall.function.name == "/")
      {
          var node = new AccBinaryOp(AccBinaryOp.Type.Div);
          node.Child1 = expressions[0];
          node.Child2 = expressions[1];

          result = node;
      }
      else if (funCall.function.name == "MAX")
      {
          var node = new AccBinaryOp(AccBinaryOp.Type.Max);
          node.Child1 = expressions[0];
          node.Child2 = expressions[1];

          result = node;
      }
      else if (funCall.function.name == "^")
      {
          var node = new AccBinaryOp(AccBinaryOp.Type.Pow);
          node.Child1 = expressions[0];
          node.Child2 = expressions[1];

          result = node;
      }
      else if (funCall.function.name == "MIN")
      {
          var node = new AccBinaryOp(AccBinaryOp.Type.Min);
          node.Child1 = expressions[0];
          node.Child2 = expressions[1];

          result = node;
      }
      else if (funCall.function.name == "MMULT")
      {
          var node = new AccBinaryOp(AccBinaryOp.Type.MMult);
          node.Child1 = expressions[0];
          node.Child2 = expressions[1];

          result = node;
      }
      else if (funCall.function.name == "SUM")
      {
          var node = new UnaryAccNode(UnaryAccNode.Type.SUM, expressions[0]);

          result = node;
      }
      else if (funCall.function.name == "SQRT")
      {
          var node = new UnaryAccNode(UnaryAccNode.Type.SQRT, expressions[0]);

          result = node;
      }
      else if (funCall.function.name == "RAND")
      {
          var node = new RandAccNode(RandAccNode.Type.RAND);
          result = node;
      }
      else if (funCall.function.name == "RANDBETWEEN")
      {
          if (funCall.es.Length == 2)
          {
              var node = new RandAccNode(expressions[0], expressions[1]);
              result = node;
          }
          else
          {
              throw new NotImplementedException("randbetween should have two number parameters");
          }
      }
      else
      {
          result = new UnknownAccNode(funCall, thisFca, sdfInfo);
      }
      Console.WriteLine("+ :" + result.GetType());
    }

    public void CallVisitor(Error error)
    {
        throw new NotImplementedException("Errors not implemented");
    }

    public void CallVisitor(NumberConst numbConst)
    {
        result = TryAccNode((float)numbConst.value.value,new AccConst((float)numbConst.value.value));
    }

    public void CallVisitor(TextConst textConst)
    {
        throw new NotImplementedException("Text constants not allowed in Accelerator");
    }
  }

}
